for
循环在关于流控制的最后一章中,我们将介绍shell的另一个循环构造。for循环与while和until循环的不同之处在于,它提供了一种在循环过程中处理序列的方法。这在编程时非常有用。因此, for
循环是bash脚本中一种流行的构造。
很自然地,通过 for
复合命令实现了for循环。在bash中, for
有两种形式。
for
:传统shell形式for
命令的原始语法如下:
for variable [ in words]; do commands done
其中 variable 是在循环执行过程中递增的变量的名称, words 是将按顺序分配给变量的可选项目列表, commands 是在循环的每次迭代中执行的命令。
for
命令在命令行上很有用。我们可以很容易地演示它是如何工作的。
xxxxxxxxxx
[me@linuxbox ~]$ for i in A B C D; do echo $i; done
A
B
C
D
在这个例子中, for
给出了一个四个单词的列表:A、B、C和D。对于一个四单词的列表,循环执行了四次。每次执行循环时,都会为变量 i
分配一个单词。在循环中,我们有一个 echo
命令,显示i的值以显示分配。与 while
和 until
循环一样, done
关键字关闭了循环。
for
真正强大的功能是我们可以创建单词列表的许多有趣的方法。例如,我们可以通过大括弧(brace)展开来实现,如下所示:
xxxxxxxxxx
[me@linuxbox ~]$ for i in {A..D}; do echo $i; done
A
B
C
D
或者我们可以使用路径名扩展,如下所示:
xxxxxxxxxx
[me@linuxbox ~]$ for i in distros*.txt; do echo "$i"; done
distros-by-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernums.txt
distros-names.txt
distros.txt
distros-vernums.txt
distros-versions.txt
路径名扩展提供了一个可以在循环中处理的良好、干净的路径名列表。需要采取的一个预防措施是检查扩展是否与某些东西相匹配。默认情况下,如果扩展名与任何文件都不匹配,则将返回通配符本身(上例中的 distors*.txt)。为了防止这种情况,我们将用以下方式在脚本中编写上述示例:
xxxxxxxxxx
for i in distros*.txt; do
if [[ -e "$i" ]]; then
echo "$i"
fi
done
通过添加文件存在性测试,我们将忽略失败的扩展。
另一种常见的单词生成方法是命令替换。
xxxxxxxxxx
# longest-word: find longest string in a file
while [[ -n "$1" ]]; do
if [[ -r "$1" ]]; then
max_word=
max_len=0
for i in $(strings "$1"); do
len="$(echo -n "$i" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$i"
fi
done
echo "$1: '$max_word' ($max_len characters)"
fi
shift
done
在这个例子中,我们查找文件中找到的最长字符串。当在命令行上给定一个或多个文件名时,此程序使用 strings
程序(包含在GNU binutils包中)在每个文件中生成可读文本“单词”列表。 for
循环依次处理每个单词,并确定当前单词是否是迄今为止找到的最长单词。当循环结束时,将显示最长的单词。
这里要注意的一点是,与我们的惯例相反,我们没有用双引号括住命令替换 $(strings "$1")
。这是因为我们实际上希望通过分词来给出我们的列表。如果我们用引号括住命令替换,它只会产生一个包含文件中每个字符串的单词。这并不是我们想要的。
如果省略 for
命令的 words 选项部分, for
默认处理位置参数。我们将修改 longest-word 脚本以使用此方法:
xxxxxxxxxx
# longest-word2: find longest string in a file
for i; do
if [[ -r "$i" ]]; then
max_word=
max_len=0
for j in $(strings "$i"); do
len="$(echo -n "$j" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$j"
fi
done
echo "$i: '$max_word' ($max_len characters)"
fi
done
正如我们所看到的,我们已经改变了最外层的循环,用 for
代替 while
。通过省略 for
命令中的单词列表,将使用位置参数。在循环中,变量i的先前实例已被更改为变量 j
。 shift
的使用也被消除了。
您可能已经注意到,在前面的每个 for
循环示例中都选择了变量 i
。为什么?除了传统之外,实际上没有具体的原因。与 for
一起使用的变量可以是任何有效的变量,但 i
是最常见的,其次是 j
和 k
。
这一传统的基础来自Fortran编程语言。在Fortran中,以字母I、J、K、L和M开头的未声明变量会自动键入为整数,而以任何其他字母开头的变量会键入为实数(带小数分数的数字)。这种行为导致程序员将变量I、J和K用于循环变量,因为在需要临时变量(通常是循环变量)时使用它们的工作量更少。
这也导致了以下基于Fortran的俏皮话:
“上帝是真实的,除非声明为整数。”
“GOD is real, unless declared integer.”
for
:C语言形式最近版本的bash添加了第二种 for
命令语法形式,类似于C编程语言中的形式。许多其他语言也支持这种形式。
for (( expression1; expression2; expression3 )); do commands done
这里 expression1、 expression2、expression3 是算术表达式, commands 是在循环的每次迭代期间要执行的命令。
在行为方面,这种形式相当于以下构造:
(( expression1 )) while (( expression2 )); do commands (( expression3 )) done
expression1 用于初始化循环的条件, expression2 用于确定循环何时完成, expression3 在循环的每次迭代结束时执行。
以下是一个典型的应用程序:
xxxxxxxxxx
# simple_counter: demo of C style for command
for (( i=0; i<5; i=i+1 )); do
echo $i
done
执行时,它会产生以下输出:
xxxxxxxxxx
[me@linuxbox ~]$ simple_counter
0
1
2
3
4
在这个例子中, expression1 用零值初始化变量 i
, expression2 允许循环继续,只要 i
的值小于5, expression3 每次重复循环时都会将 i
的值递增1。
for
的C语言形式在需要数字序列时非常有用。我们将在接下来的两章中看到几个应用程序。
根据我们对 for
命令的了解,我们现在将对 sys_info_page 脚本进行最后的改进。目前, report_home_space 函数看起来像这样:
xxxxxxxxxx
report_home_space () {
if [[ "$(id -u)" -eq 0 ]]; then
cat << _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
else
cat << _EOF_
<h2>Home Space Utilization ($USER)</h2>
<pre>$(du -sh "$HOME")</pre>
_EOF_
fi
return
}
接下来,我们将重写它,为每个用户的主目录提供更多详细信息,并包括每个目录中的文件和子目录的总数。
xxxxxxxxxx
report_home_space () {
local format="%8s%10s%10s\n"
local i dir_list total_files total_dirs total_size user_name
if [[ "$(id -u)" -eq 0 ]]; then
dir_list=/home/*
user_name="All Users"
else
dir_list="$HOME"
user_name="$USER"
fi
echo "<h2>Home Space Utilization ($user_name)</h2>"
for i in $dir_list; do
total_files="$(find "$i" -type f | wc -l)"
total_dirs="$(find "$i" -type d | wc -l)"
total_size="$(du -sh "$i" | cut -f 1)"
echo "<H3>$i</H3>"
echo "<pre>"
printf "$format" "Dirs" "Files" "Size"
printf "$format" "----" "-----" "----"
printf "$format" "$total_dirs" "$total_files" "$total_size"
echo "</pre>"
done
return
}
这次重写应用了我们迄今为止学到的大部分知识。我们仍然在测试超级用户,但我们没有在 if
中执行完整的操作集,而是在 for
循环中设置了一些稍后使用的变量。我们在函数中添加了几个局部变量,并使用 printf
格式化了一些输出。